{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "36800e60",
   "metadata": {},
   "source": [
    "## Relational Graph LSTM Convolution Layer\n",
    "Temporal implementation of the RGCNConv model from PyTorcch Geometric. <br />\n",
    "\n",
    "Similar to a normal LSTM, but takes an integer label for the type of edge instead of a weight. <br />\n",
    "Otherwise usage is analogous to a standard pytorch LSTMCell. <br />"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9a8b2e8",
   "metadata": {},
   "source": [
    "### LRGCN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "7e473b3a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 200/200 [21:04<00:00,  6.32s/it]\n",
      "C:\\Users\\TBROFIP4\\Miniconda3\\envs\\py37\\lib\\site-packages\\ipykernel_launcher.py:113: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train MSE: 0.1207\n",
      "Test MSE: 0.0990\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "from tqdm import tqdm\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import torch\n",
    "from torch import nn\n",
    "from torch_geometric_temporal.nn.recurrent import LRGCN\n",
    "\n",
    "from torch_geometric_temporal.dataset import ChickenpoxDatasetLoader\n",
    "from torch_geometric_temporal.signal import temporal_signal_split\n",
    "\n",
    "loader = ChickenpoxDatasetLoader()\n",
    "\n",
    "lags = 10\n",
    "stride = 1\n",
    "epochs = 200\n",
    "\n",
    "dataset = loader.get_dataset(lags)\n",
    "\n",
    "train_dataset, test_dataset = temporal_signal_split(dataset, train_ratio=0.2)\n",
    "\n",
    "### MODEL DEFINITION\n",
    "class RecurrentGCN(nn.Module):\n",
    "    def __init__(self, node_features, window_length):\n",
    "        super(RecurrentGCN, self).__init__()\n",
    "        \n",
    "        self.node_features = node_features\n",
    "        self.n = window_length\n",
    "        self.hidden_state_size = 64\n",
    "        assert self.hidden_state_size % 2 == 0\n",
    "                \n",
    "        self.recurrent = LRGCN(node_features, self.hidden_state_size, 1, 1)\n",
    "        self.linear1 = nn.Sequential(\n",
    "            nn.Linear(self.hidden_state_size, self.hidden_state_size//2), \n",
    "            nn.Dropout(p=0.2),\n",
    "            nn.ReLU()\n",
    "        )\n",
    "        self.out = nn.Sequential(\n",
    "            nn.Linear(self.hidden_state_size//2, 1),\n",
    "            nn.Flatten(start_dim=0, end_dim=-1)\n",
    "        )\n",
    "\n",
    "    def forward(self, window, h, c):        \n",
    "        edge_index = window.edge_index\n",
    "        edge_attr = window.edge_attr\n",
    "    \n",
    "        H, C = [], []\n",
    "        \n",
    "        for t in range(self.n):\n",
    "            x = window.x[:,t].unsqueeze(0).T\n",
    "            h, c = self.recurrent(x, edge_index, edge_attr, h, c)\n",
    "            \n",
    "            H.append(h.detach())\n",
    "            C.append(c.detach())\n",
    "            \n",
    "        x = self.linear1(h)\n",
    "        pred = self.out(x)\n",
    "\n",
    "        return pred, H, C\n",
    "    \n",
    "model = RecurrentGCN(node_features=1, window_length=10)\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.01)\n",
    "\n",
    "\n",
    "### TRAIN\n",
    "model.train()\n",
    "\n",
    "loss_history = []\n",
    "for _ in tqdm(range(epochs)):\n",
    "    h, c = None, None\n",
    "    total_loss = 0\n",
    "    for i, window in enumerate(train_dataset):\n",
    "        optimizer.zero_grad()\n",
    "        \n",
    "        y_pred, H, C = model(window, h, c)\n",
    "\n",
    "        h = H[stride-1]\n",
    "        c = C[stride-1]\n",
    "        \n",
    "        assert y_pred.shape == window.y.shape\n",
    "        loss = torch.mean((y_pred - window.y)**2)\n",
    "        total_loss += loss.item()\n",
    "        \n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "    total_loss /= i+1\n",
    "    loss_history.append(total_loss)\n",
    "\n",
    "### TEST \n",
    "model.eval()\n",
    "loss = 0\n",
    "with torch.no_grad():\n",
    "    h, c = None, None\n",
    "    for i, window in enumerate(test_dataset):\n",
    "        y_pred, H, C = model(window, h, c)\n",
    "        h = H[stride-1]\n",
    "        c = C[stride-1]\n",
    "        \n",
    "        assert y_pred.shape == window.y.shape\n",
    "        loss += torch.mean((y_pred - window.y)**2)\n",
    "    loss /= i+1\n",
    "\n",
    "### RESULTS PLOT\n",
    "fig, ax = plt.subplots()\n",
    "\n",
    "x_ticks = np.arange(1, epochs+1)\n",
    "ax.plot(x_ticks, loss_history)\n",
    "\n",
    "# figure labels\n",
    "ax.set_title('Loss over time', fontweight='bold')\n",
    "ax.set_xlabel('Epochs', fontweight='bold')\n",
    "ax.set_ylabel('Mean Squared Error', fontweight='bold')\n",
    "plt.show()\n",
    "\n",
    "print(\"Train MSE: {:.4f}\".format(total_loss))\n",
    "print(\"Test MSE: {:.4f}\".format(loss))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4d234a4c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "9dc1092468b6c95b716c12f4a89881ec47ccb10a5d5fac9cc7256670f9aa8e17"
  },
  "kernelspec": {
   "display_name": "Python 3.7.0 64-bit ('py37': conda)",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
